import { Precision } from "../Precision";
import { XY } from "../coord/XY";

/**
 * @description 二维向量
 * @author xuld
 * @date 2024-11-08
 * @lastEditTime 2024-11-08 17:43:44
 * @lastEditors xuld
 */
export class Vector2 {
    /**
     * @description 内部坐标对象
     */
    private _coord: XY;

    /**
     * @description 创建二维向量
     * @param x - X坐标值，默认为0
     * @param y - Y坐标值，默认为0
     */
    constructor(x: number = 0, y: number = 0) {
        this._coord = new XY(x, y);
    }

    /**
     * @description 获取X坐标值
     */
    public get x(): number {
        return this._coord.x;
    }

    /**
     * @description 设置X坐标值
     */
    public set x(value: number) {
        this._coord.x = value;
    }

    /**
     * @description 获取Y坐标值
     */
    public get y(): number {
        return this._coord.y;
    }

    /**
     * @description 设置Y坐标值
     */
    public set y(value: number) {
        this._coord.y = value;
    }

    /**
     * @description 获取内部坐标对象
     */
    public get coord(): XY {
        return this._coord;
    }

    /**
     * @description 归一化向量，返回新向量
     * @param len - 归一化长度，默认为1
     * @returns 归一化后的新向量
     */
    public normalize(len: number = 1): Vector2 {
        let vec = new Vector2(this.x, this.y);
        vec.normalizeSelf();
        vec.x *= len;
        vec.y *= len;
        return vec;
    }

    /**
     * @description 对自身进行归一化
     */
    public normalizeSelf(): void {
        this._coord.normalizeSelf();
    }

    /**
     * @description 获取向量的模长
     */
    public get magnitude(): number {
        return this._coord.modulus();
    }

    /**
     * @description 获取向量模长的平方
     */
    public get squareMagnitude(): number {
        return this._coord.squareModulus();
    }

    /**
     * @description 向量加法，返回新向量
     * @param other - 要相加的向量
     * @returns 相加后的新向量
     */
    public add(other: Vector2): Vector2 {
        let newVec = new Vector2(this.x + other.x, this.y + other.y);
        return newVec;
    }

    /**
     * @description 向量加法，结果存储在自身
     * @param other - 要相加的向量
     */
    public addSelf(other: Vector2): void {
        this._coord.x += other.x;
        this._coord.y += other.y;
    }

    /**
     * @description 向量减法，返回新向量
     * @param other - 要相减的向量
     * @returns 相减后的新向量
     */
    public subtract(other: Vector2): Vector2 {
        let newVec = new Vector2(this.x - other.x, this.y - other.y);
        return newVec;
    }

    /**
     * @description 向量减法，结果存储在自身
     * @param other - 要相减的向量
     */
    public subtractSelf(other: Vector2): void {
        this._coord.x -= other.x;
        this._coord.y -= other.y;
    }

    /**
     * @description 向量乘以标量，返回新向量
     * @param scalar - 标量值
     * @returns 相乘后的新向量
     */
    public multiply(scalar: number): Vector2 {
        let newVec = new Vector2(this.x, this.y);
        newVec.multiplySelf(scalar);
        return newVec;
    }

    /**
     * @description 向量乘以标量，结果存储在自身
     * @param scalar - 标量值
     */
    public multiplySelf(scalar: number): void {
        this._coord.multiplySelf(scalar);
    }

    /**
     * @description 向量除以标量，返回新向量
     * @param scalar - 标量值
     * @returns 相除后的新向量
     * @throws 当除数为0时抛出错误
     */
    public divide(scalar: number): Vector2 {
        if (scalar === 0) {
            throw new Error("Division by zero");
        }
        let newVec = new Vector2(this.x, this.y);
        newVec.divideSelf(scalar);
        return newVec;
    }

    /**
     * @description 向量除以标量，结果存储在自身
     * @param scalar - 标量值
     * @throws 当除数为0时抛出错误
     */
    public divideSelf(scalar: number): void {
        if (scalar === 0) {
            throw new Error("Division by zero");
        }
        this._coord.divideSelf(scalar);
    }

    /**
     * @description 计算两个向量的点积
     * @param other - 另一个向量
     * @returns 点积结果
     */
    public dot(other: Vector2): number {
        return this._coord.dot(other._coord);
    }

    /**
     * @description 计算两个向量的叉积
     * @param other - 另一个向量
     * @returns 叉积结果
     */
    public cross(other: Vector2): number {
        return this._coord.cross(other._coord);
    }

    /**
     * @description 向量取反，返回新向量
     * @returns 取反后的新向量
     */
    public negate(): Vector2 {
        let newVec = new Vector2(this.x, this.y);
        newVec.negateSelf();
        return newVec;
    }

    /**
     * @description 向量取反，结果存储在自身
     */
    public negateSelf(): void {
        this._coord.negateSelf();
    }

    /**
     * @description 向量旋转90度，返回新向量
     * @returns 旋转后的新向量
     */
    public rotate90(): Vector2 {
        let newVec = this.clone();
        newVec.rotate90Self();
        return newVec;
    }

    /**
     * @description 向量自身旋转90度
     */
    public rotate90Self(): void {
        let tempY = this.y;
        this.y = this.x;
        this.x = -tempY;
    }

    /**
     * @description 向量旋转270度，返回新向量
     * @returns 旋转后的新向量
     */
    public rotate270(): Vector2 {
        let newVec = this.clone();
        newVec.rotate270Self();
        return newVec;
    }

    /**
     * @description 向量自身旋转270度
     */
    public rotate270Self(): void {
        let tempY = this.y;
        this.y = -this.x;
        this.x = tempY;
    }

    /**
     * @description 计算到另一个向量的旋转角度
     * @param other - 另一个向量
     * @param otherIsUnit - 另一个向量是否已经单位化，默认为false
     * @returns 旋转角度（弧度），范围[0, 2π)
     */
    public angleTo(other: Vector2, otherIsUnit: boolean = false): number {
        let angleBetween: number = this.angleBetween(other, otherIsUnit);
        let cross = this.cross(other);
        let angle: number = cross < 0 ? 6.283185307179586 - angleBetween : angleBetween;
        return angle;
    }

    /**
     * @description 计算与另一个向量的夹角
     * @param other - 另一个向量
     * @param otherIsUnit - 另一个向量是否已经单位化，默认为false
     * @returns 夹角（弧度），范围[0, π]
     */
    public angleBetween(other: Vector2, otherIsUnit: boolean = false): number {
        let otherNormalize: Vector2;
        if (otherIsUnit) {
            otherNormalize = other;
        } else {
            let otherModule = Math.sqrt(other.x * other.x + other.y * other.y);
            let otherX = other.x / otherModule;
            let otherY = other.y / otherModule;
            otherNormalize = new Vector2(otherX, otherY);
        }
        let thisNormalize = this.normalize();
        let dot: number = thisNormalize.dot(otherNormalize);
        dot = Math.max(-1, Math.min(1, dot));
        let angle = Math.acos(dot);
        return angle;
    }

    /**
     * @description 判断两个向量是否相等
     * @param other - 比较的向量
     * @param tolerance - 容差值，默认为EPSILON3
     * @returns 是否相等
     */
    public equals(other: Vector2, tolerance: number = Precision.EPSILON3): boolean {
        return this._coord.equals(other._coord, tolerance);
    }

    /**
     * @description 从其他向量复制坐标值
     * @param other - 要复制的向量
     */
    public copyFrom(other: Vector2): void {
        this.coord.x = other.x;
        this.coord.y = other.y;
    }

    /**
     * @description 创建当前向量的副本
     * @returns 新的向量对象
     */
    public clone(): Vector2 {
        return new Vector2(this.x, this.y);
    }

    /**
     * @description 转换为字符串表示
     * @returns 格式化的向量字符串
     */
    public toString(): string {
        return `{"type": "Vector2", "x": ${this.x}, "y": ${this.y}}`;
    }

    /**
     * @description 向量绕中心点旋转
     * @param radian - 旋转弧度
     * @param center - 旋转中心点，默认为原点(0,0)
     * @returns 旋转后的新向量
     */
    public rotate(radian: number, center?: XY): Vector2 {
        if (center) {
            let p1 = new XY(0, 0);
            p1.rotateSelf(radian, center);
            let p2 = this.coord.rotate(radian, center);
            let dir = p2.subtract(p1);
            return new Vector2(dir.x, dir.y);
        }
        else {
            let p = this.coord.rotate(radian, center);
            return new Vector2(p.x, p.y);
        }
    }

    /**
     * @description 向量自身绕中心点旋转
     * @param radian - 旋转弧度
     * @param center - 旋转中心点，默认为原点(0,0)
     */
    public rotateSelf(radian: number, center?: XY): void {
        if (center) {
            let p1 = new XY(0, 0);
            p1.rotateSelf(radian, center);
            let p2 = this.coord.rotate(radian, center);
            let dir = p2.subtract(p1);
            this.x = dir.x;
            this.y = dir.y;
        }
        else {
            this.coord.rotateSelf(radian, center);
        }
    }

    /**
     * @description 获取向量长度
     */
    length(): number {
        return Math.sqrt(this.coord.x * this.coord.x + this.coord.y * this.coord.y);
    }
}